#include "config.h"

#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#define DBG_SUBSYS S_LIBTASK

#include "lich_api.h"
#include "nodectl.h"
#include "recovery.h"

/**
 * @file 按pool組織recovery過程
 *
 * @todo 处理pool被删的情况
 */

typedef struct {
        sy_rwlock_t lock;
        hashtable_t tab;
} recovery_out_module_t;

recovery_out_module_t *module = NULL;

// pool hash
static uint32_t __recovery_key(const void *i)
{
        return hash_str((char *)i);
}

static int __recovery_cmp(const void *v1, const void *v2)
{
        const recovery_out_t *out = v1;
        const char *name = v2;

        return strcmp(out->pool_name, name);
}

static inline const char *_recovery_disk_status2str(int status)
{
        if (status == __RECOVERY_RUNNING__)
                return "running";
        else if (status == __RECOVERY_WAITING__)
                return "waiting";
        else if (status == __RECOVERY_SCANNING__)
                return "scanning";
        else
                return "suspend";
}

static void recovery_out_dump(recovery_out_t *out, int new_status)
{
        char value[MAX_BUF_LEN];
        char path[MAX_NAME_LEN];

        if ((new_status == __RECOVERY_WAITING__)) {
                out->lastscan = gettime();
                out->recovery = 0;
                out->lost = 0;
                out->offline = 0;
                out->success = 0;
                out->fail = 0;
                out->speed = 0;
        }

        if (new_status != -1) {
                DINFO("pool_name:%s recovery_status: %s -> %s\n",
                      out->pool_name,
                      _recovery_disk_status2str(out->status),
                      _recovery_disk_status2str(new_status));

                out->status = new_status;
        }

        snprintf(value, MAX_NAME_LEN,
                 "status:%s\n"
                         "recovery:%ju\n"
                         "success:%ju\n"
                         "success_total:%ju\n"
                         "fail:%ju\n"
                         "lost:%ju\n"
                         "offline:%ju\n"
                         "speed:%ju\n"
                         "lastscan:%u\n",
                 _recovery_disk_status2str(new_status),
                 out->recovery,
                 out->success,
                 out->success_total,
                 out->fail,
                 out->lost,
                 out->offline,
                 out->speed,
                 (int)out->lastscan);

        memset (path, 0, MAX_NAME_LEN);
        sprintf(path, RECOVERY"%s"RECOVERY_DISK_INFO, out->pool_name);

        nodectl_set(path, value);
}

STATIC int recovery_out_get(const char *pool, recovery_out_t **out)
{
        int ret;
        recovery_out_t *_out = NULL;

        *out = NULL;

        ret = sy_rwlock_wrlock(&module->lock);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        _out = hash_table_find(module->tab, (void *)pool);
        if (_out == NULL) {
                ret = ymalloc((void **)&_out, sizeof(recovery_out_t));
                if (unlikely(ret))
                        GOTO(err_lock, ret);

                ret = sy_rwlock_init(&_out->lock, "out_lock");
                if (unlikely(ret))
                        GOTO(err_free, ret);

                strcpy(_out->pool_name, pool);
                timerange1_init(&_out->range, "diskmd_rec", 1000 * 1000);
        }

        _out->refcount++;
        *out = _out;

        sy_rwlock_unlock(&module->lock);

        return 0;
err_free:
        yfree((void **)&_out);
err_lock:
        sy_rwlock_unlock(&module->lock);
err_ret:
        return ret;
}

STATIC int recovery_out_release(recovery_out_t *out)
{
        int ret;

        ret = sy_rwlock_wrlock(&module->lock);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        out->refcount--;

        sy_rwlock_unlock(&module->lock);

        return 0;
err_ret:
        return ret;
}

STATIC int __recovery_out_inc(const char *pool, int type, int inc)
{
        int ret;
        recovery_out_t *out;

        ret = recovery_out_get(pool, &out);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = sy_rwlock_wrlock(&out->lock);
        if (unlikely(ret))
                GOTO(err_get, ret);

        if (type == 1) {
                out->recovery += inc;
                out->offline += inc;
        } else if (type == 2) {
                out->lost += inc;
        } else if (type == 3) {
                out->success += inc;
                out->success_total += inc;

                if (out->success_total % 1000 == 0) {
                        set_recovery_total(out->pool_name, out->success_total, TRUE);
                }
        } else if (type == 4) {
                out->fail += inc;
        }

        sy_rwlock_unlock(&out->lock);

        recovery_out_release(out);
        return 0;
err_get:
        recovery_out_release(out);
err_ret:
        return ret;
}

int recovery_out_add_recovery(const char *pool, int inc)
{
        return __recovery_out_inc(pool, 1, inc);
}

int recovery_out_add_lost(const char *pool, int inc)
{
        return __recovery_out_inc(pool, 2, inc);
}

int recovery_out_add_success(const char *pool, int inc)
{
        return __recovery_out_inc(pool, 3, inc);
}

int recovery_out_add_fail(const char *pool, int inc)
{
        return __recovery_out_inc(pool, 4, inc);
}

int recovery_out_set_status(const char *pool, int status)
{
        int ret;
        recovery_out_t *out;

        ret = recovery_out_get(pool, &out);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = sy_rwlock_wrlock(&out->lock);
        if (unlikely(ret))
                GOTO(err_get, ret);

        if (status == __RECOVERY_WAITING__) {
                out->lastscan = gettime();
                out->recovery = 0;
                out->lost = 0;
                out->offline = 0;
                out->success = 0;
                out->fail = 0;
                out->speed = 0;
        }

        if (status != -1) {
                out->status = status;
        }

        recovery_out_dump(out, status);

        sy_rwlock_unlock(&out->lock);
        recovery_out_release(out);
        return 0;
err_get:
        recovery_out_release(out);
err_ret:
        return ret;
}

int recovery_out_flush(const char *pool)
{
        int ret;
        recovery_out_t *out;

        ret = recovery_out_get(pool, &out);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = sy_rwlock_wrlock(&out->lock);
        if (unlikely(ret))
                GOTO(err_get, ret);

        set_recovery_total(out->pool_name, out->success_total, TRUE);

        sy_rwlock_unlock(&out->lock);
        recovery_out_release(out);
        return 0;
err_get:
        recovery_out_release(out);
err_ret:
        return ret;
}

int recovery_out_get_speed(const char *pool, uint64_t *speed)
{
        int ret;
        recovery_out_t *out;

        ret = recovery_out_get(pool, &out);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = sy_rwlock_wrlock(&out->lock);
        if (unlikely(ret))
                GOTO(err_get, ret);

        *speed = out->speed;

        sy_rwlock_unlock(&out->lock);
        recovery_out_release(out);
        return 0;
err_get:
        recovery_out_release(out);
err_ret:
        return ret;
}

static int __out_iter(void *arg1, void *arg2)
{
        (void) arg1;
        recovery_out_t *out = arg2;

        if (timerange1_update(&out->range, out->success)) {
                out->speed = out->range.speed;
                recovery_out_dump(out, -1);
        }

        return 0;
}

static void *__recovery_worker(void *_ent)
{
        (void) _ent;

        while (1) {
                usleep(1000 * 1000);

                hash_iterate_table_entries(module->tab, __out_iter, NULL);
        }

        return NULL;
}

int recovery_out_init()
{
        int ret;

        if (module == NULL) {
                ret = ymalloc((void **)&module, sizeof(recovery_out_module_t));
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                ret = sy_rwlock_init(&module->lock, "recovery_out");
                if (unlikely(ret))
                        GOTO(err_free, ret);

                module->tab = hash_create_table(__recovery_cmp, __recovery_key, "recovery_out_table");
                if (module->tab == NULL) {
                        ret = ENOMEM;
                        DERROR("ret (%d) %s\n", ret, strerror(ret));
                        GOTO(err_lock, ret);
                }

                ret = sy_thread_create2(__recovery_worker, NULL, "recovery_out_init");
                if (unlikely(ret))
                        GOTO(err_lock, ret);
        }

        return 0;
err_lock:
        sy_rwlock_destroy(&module->lock);
err_free:
        yfree((void **)&module);
err_ret:
        return ret;
}
